package com.exmple.myapplication;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.io.File;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;

import jp.co.canon.android.print.ij.usbsdk.CanonCleanUpCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonControlCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonPermissionRequestCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonPreparationCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintDevice;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintDevice.PrintableStatus;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintDeviceBase.ControlCommand;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintDeviceBase.ControlResult;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintJob;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintJob.Configuration;
import jp.co.canon.android.print.ij.usbsdk.CanonPrintParams;
import jp.co.canon.android.print.ij.usbsdk.CanonPrinterBatteryInfo;
import jp.co.canon.android.print.ij.usbsdk.CanonPrinterInkInfo;
import jp.co.canon.android.print.ij.usbsdk.CanonPrinterStatus;
import jp.co.canon.android.print.ij.usbsdk.CanonStatusCallback;
import jp.co.canon.android.print.ij.usbsdk.CanonUsbManager;
//佳能sdk demo示例
public class PrintDemoFragment extends Fragment {
    private static final int MANUAL_DUPLEX_SETTING_OFF = 0;
    private static final int MANUAL_DUPLEX_SETTING_ON = 1;
    private CanonPrintDevice mSelectedPrinter;
    private Uri mSelectedFile;
    private CanonPrintJob mJob = createJob();
    private int mScaling = CanonPrintParams.SCALING_NONE;
    private int mPaperOrientation = CanonPrintParams.PAPERORIENTATION_AUTOMATIC;
    private int mManualDuplex = CanonPrintParams.MANUAL_DUPLEX_SETTING_OFF;
    private final ThreadFactory factory = r -> new Thread(r, "s");
    private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2, factory);
    private final Handler mHandler = new Handler(Looper.myLooper());
    private ActivityResultLauncher<String> getFileActivityResultLauncher;
    private View rootView;

    @SuppressLint("InflateParams")
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.fragment_set, null);

        return rootView;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
            boolean b = true;
            for (Boolean bl : result.values()) {
                b = b & bl;
            }
            if (!b) {
                Toast.makeText(getContext(), "获取失败", Toast.LENGTH_SHORT).show();
            }
        }).launch(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE});
        getFileActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
            if (result != null) {
                mSelectedFile = result;
                TextView txt = rootView.findViewById(R.id.file_path_text);
                txt.setText(getFileName(mSelectedFile));
            }
        });
        // File picker button
        rootView.findViewById(R.id.file_picker_button).setOnClickListener(v -> openFileBySaf());

        // Printer button
        rootView.findViewById(R.id.printer_button).setOnClickListener(v -> getPrinterList());

        // Config buttons
        Button button;
        button = rootView.findViewById(R.id.media).findViewById(R.id.button);
        button.setText(R.string.media);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.MediaType));

        button = rootView.findViewById(R.id.size).findViewById(R.id.button);
        button.setText(R.string.size);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.PaperSize));

        button = rootView.findViewById(R.id.borderless).findViewById(R.id.button);
        button.setText(R.string.borderless);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.Borderless));

        button = rootView.findViewById(R.id.color).findViewById(R.id.button);
        button.setText(R.string.color);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.ColorMode));

        button = rootView.findViewById(R.id.duplex).findViewById(R.id.button);
        button.setText(R.string.duplex);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.Duplex));

        button = rootView.findViewById(R.id.copy).findViewById(R.id.button);
        button.setText(R.string.copy);
        button.setOnClickListener(v -> openPrintConfigDialog(getContext(), Configuration.Copies));

        button = rootView.findViewById(R.id.quality).findViewById(R.id.button);
        button.setText(R.string.quality);
        button.setOnClickListener(view -> {
            if (mSelectedPrinter == null) {
                return;
            }

            // Gets print configuration parameters
            final Hashtable<Configuration, ArrayList<Integer>> params = mSelectedPrinter.getParameterList();
            if (params == null) {
                return;
            }
            ArrayList<Integer> items = params.get(Configuration.Quality);
            if (items == null || items.contains(CanonPrintParams.QUALITY_NOT_SUPPORT) || items.size() == 0) {
                mJob.setPrintConfiguration(Configuration.Quality, CanonPrintJob.PRINTJOB_INVALID_VALUE);
                ((TextView) rootView.findViewById(R.id.quality).findViewById(R.id.value)).setText(R.string.quality_not_support);
                showToast(getString(R.string.quality_not_support));
                return;
            }

            openPrintConfigDialog(getContext(), Configuration.Quality);
        });

        button = rootView.findViewById(R.id.manual_duplex).findViewById(R.id.button);
        button.setText(R.string.manual_duplex);
        button.setOnClickListener(view -> openManualDuplexConfigDialog());

        button = rootView.findViewById(R.id.orientation).findViewById(R.id.button);
        button.setText(R.string.orientation);
        button.setOnClickListener(view -> openOrientationConfigDialog());

        button = rootView.findViewById(R.id.scaling).findViewById(R.id.button);
        button.setText(R.string.scaling);
        button.setOnClickListener(view -> openScalingConfigDialog());

        // Get Status button
        rootView.findViewById(R.id.status_button).setOnClickListener(v -> executeGetStatus());

        // clean up button
        rootView.findViewById(R.id.clean_button).setOnClickListener(v -> executeCleanUp());

        // Power off button
        rootView.findViewById(R.id.power_off_button).setOnClickListener(v -> executePowerOff());

        // Reset button
        rootView.findViewById(R.id.reset_button).setOnClickListener(v -> executeReset());

        // Prepare button
        rootView.findViewById(R.id.prepare_button).setOnClickListener(v -> executePrepare());

        // Print button
        rootView.findViewById(R.id.print_button).setOnClickListener(v -> executePrint());

        setEnableAllButtonAboutPrinter(false);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mJob != null) {
            mJob.cancel();
        }
    }


    private String getFileName(Uri uri) {
        if (uri == null) {
            return null;
        }

        // get scheme
        String scheme = uri.getScheme();
        if (scheme == null) {
            return null;
        }

        // get file name
        String fileName = null;
        switch (scheme) {
            case "content":
                String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
                Cursor cursor = this.getContext().getContentResolver()
                        .query(uri, projection, null, null, null);
                if (cursor != null) {
                    if (cursor.moveToFirst()) {
                        fileName = cursor.getString(
                                cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME));
                    }
                    cursor.close();
                }
                break;

            case "file":
                String path = uri.getPath();
                if (path == null) {
                    return null;
                }
                fileName = new File(path).getName();
                break;

            default:
                break;
        }
        return fileName;
    }

    private void openFileBySaf() {
        getFileActivityResultLauncher.launch("*/*");

    }

    private CanonPrintJob createJob() {
        CanonPrintJob job = new CanonPrintJob();
        if (mSelectedPrinter != null) {
            job.setDefaultPrintConfiguration(getContext(), mSelectedPrinter);
        }
        return job;
    }

    private void getPrinterList() {
        final Context applicationContext = this.getContext();

        try {
            List<CanonPrintDevice> printerList = CanonUsbManager.getPrinterList(applicationContext);
            openPrinterDialog(printerList);
        } catch (SecurityException e) {
            CanonUsbManager.prepareToGetPrinterList(applicationContext, new CanonPreparationCallback() {
                @Override
                public void onSuccess() {
                    List<CanonPrintDevice> printerList = CanonUsbManager.getPrinterList(applicationContext);
                    openPrinterDialog(printerList);
                }

                @Override
                public void onFailure() {
                    showToast("获取打印机列表失败。");
                }
            });
        }
    }

    private void openPrinterDialog(List<CanonPrintDevice> printerList) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle("选择打印机:");

        final Context applicationContext = this.getContext();

        final List<CanonPrintDevice> canonPrinterList = printerList;
        List<String> printers = new ArrayList<>();

        if (canonPrinterList != null) {
            for (CanonPrintDevice device : canonPrinterList) {
                printers.add(device.getPrinterName());
            }
        }

        String[] printerNameArray = printers.toArray(new String[0]);

        builder.setItems(
                printerNameArray,
                (dialog, which) -> {
                    if (canonPrinterList != null) {
                        final CanonPrintDevice selected = canonPrinterList.get(which);

                        if (CanonUsbManager.hasPermission(applicationContext, selected)) {
                            setSelectedPrinter(selected);
                        } else {
                            boolean result = CanonUsbManager.requestPermission(
                                    applicationContext,
                                    selected,
                                    new CanonPermissionRequestCallback() {
                                        @Override
                                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                                            if (selected.equals(device)) {
                                                setSelectedPrinter(device);
                                            }
                                        }
                                    }
                            );
                            if (!result) {
                                showToast(getString(R.string.failed_to_request_permission));
                            }
                        }

                    }
                });
        builder.show();
    }

    private void setSelectedPrinter(CanonPrintDevice device) {
        mSelectedPrinter = device;

        TextView printerNameTextView = rootView.findViewById(R.id.printer_name_text);
        TextView serialNumberTextView = rootView.findViewById(R.id.printer_serial_number_text);

        if (mSelectedPrinter != null) {

            String text = mSelectedPrinter.toString();
            printerNameTextView.setText(text);
            serialNumberTextView.setText(mSelectedPrinter.getSerialNumber());


            mJob.setDefaultPrintConfiguration(getContext(), mSelectedPrinter);
            setEnableAllButtonAboutPrinter(true);

        } else {
            // initialize
            printerNameTextView.setText(getString(R.string.none));
            serialNumberTextView.setText("");

            mJob = createJob();
            setEnableAllButtonAboutPrinter(false);
        }

        updateConfigTexts();
    }

    private void setEnableAllButtonAboutPrinter(boolean enabled) {
        rootView.findViewById(R.id.media).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.size).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.borderless).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.color).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.duplex).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.copy).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.quality).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.orientation).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.scaling).findViewById(R.id.button).setEnabled(enabled);
        rootView.findViewById(R.id.manual_duplex).findViewById(R.id.button).setEnabled(enabled);

        rootView.findViewById(R.id.reset_button).setEnabled(enabled);
        rootView.findViewById(R.id.power_off_button).setEnabled(enabled);
        rootView.findViewById(R.id.prepare_button).setEnabled(enabled);
        rootView.findViewById(R.id.status_button).setEnabled(enabled);
        rootView.findViewById(R.id.clean_button).setEnabled(enabled);
    }

    private void openPrintConfigDialog(Context context, final Configuration configuration) {
        if (mSelectedPrinter == null) {
            return;
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle(getString(R.string.select_value, updateConfigurationTexts(configuration.ordinal())));

        // Gets print configuration parameters
        final Hashtable<Configuration, ArrayList<Integer>> paramMap = mSelectedPrinter.getParameterList();
        if (paramMap == null) {
            return;
        }

        // Gets strings from resources
        String[] displayNames = null;
        if (configuration == Configuration.MediaType) {
            displayNames = context.getResources().getStringArray(R.array.mediaList);
        } else if (configuration == Configuration.PaperSize) {
            displayNames = context.getResources().getStringArray(R.array.paperSize);
        } else if (configuration == Configuration.Borderless) {
            displayNames = context.getResources().getStringArray(R.array.borderlessPrint);
        } else if (configuration == Configuration.ColorMode) {
            displayNames = context.getResources().getStringArray(R.array.colorMode);
        } else if (configuration == Configuration.Duplex) {
            displayNames = context.getResources().getStringArray(R.array.duplexPrint);
        } else if (configuration == Configuration.Quality) {
            displayNames = context.getResources().getStringArray(R.array.qualityPrint);
        }

        List<String> paramList = new ArrayList<>();
        if (displayNames != null) {
            ArrayList<Integer> params = paramMap.get(configuration);
            if (params != null) {
                for (int param : params) {
                    try {
                        paramList.add(displayNames[param - 1]);
                    } catch (Exception e) {
                        // no string res
                    }
                }
            }
        } else {
            // Copy
            for (int i = 1; i < 6; i++) {
                paramList.add(i + "");
            }
        }

        final String[] paramArray = paramList.toArray(new String[0]);
        builder.setItems(paramArray, (dialog, which) -> {
            List<Integer> paramsList = paramMap.get(configuration);
            if (paramsList != null) {
                Integer param = paramsList.get(which);
                // When select custom paper size
                if (configuration == Configuration.PaperSize) {
                    if (param == CanonPrintParams.PAPERSIZE_USERDEFINITION) {
                        List<Integer> customSize = paramMap.get(Configuration.UserDefinitionSize);
                        // minWidth,maxWidth,minLength,maxLength
                        if (customSize == null || customSize.size() != 4) {
                            showToast("无法获取自定义用纸设置信息！");
                            return;
                        }
                        Integer[] sizeArray = customSize.toArray(new Integer[4]);
                        TextView tvMinMaxWidth = (TextView) rootView.findViewById(R.id.min_max_width);
                        TextView tvMinMaxHeight = (TextView) rootView.findViewById(R.id.min_max_height);
                        tvMinMaxWidth.setText("(" + sizeArray[0] / 100 + " - " + sizeArray[1] / 100 + ")毫米");
                        tvMinMaxHeight.setText(String.format("(%d - %d)毫米", sizeArray[2] / 100, sizeArray[3] / 100));

                        rootView.findViewById(R.id.size_width).setVisibility(View.VISIBLE);
                        rootView.findViewById(R.id.size_height).setVisibility(View.VISIBLE);
                    } else {
                        rootView.findViewById(R.id.size_width).setVisibility(View.GONE);
                        rootView.findViewById(R.id.size_height).setVisibility(View.GONE);
                    }
                }
                mJob.setPrintConfiguration(configuration, param);
            } else {
                // Copy
                mJob.setPrintConfiguration(configuration, which + 1);
            }

            updateConfigTexts();
        });

        builder.show();
    }

    private void updateConfigTexts() {
        updateConfigText(
                R.id.media, R.array.mediaList,
                mJob.getPrintConfiguration(Configuration.MediaType)
        );

        updateConfigText(
                R.id.size, R.array.paperSize,
                mJob.getPrintConfiguration(Configuration.PaperSize)
        );

        updateConfigText(
                R.id.borderless, R.array.borderlessPrint,
                mJob.getPrintConfiguration(Configuration.Borderless)
        );

        updateConfigText(
                R.id.color, R.array.colorMode,
                mJob.getPrintConfiguration(Configuration.ColorMode)
        );

        updateConfigText(
                R.id.duplex, R.array.duplexPrint,
                mJob.getPrintConfiguration(Configuration.Duplex)
        );

        updateConfigText(
                R.id.quality, R.array.qualityPrint,
                mJob.getPrintConfiguration(Configuration.Quality)
        );

        // copy
        ((TextView) rootView.findViewById(R.id.copy).findViewById(R.id.value))
                .setText(String.format(Locale.ENGLISH, "%d",
                        mJob.getPrintConfiguration(Configuration.Copies)));

        ((TextView) rootView.findViewById(R.id.orientation).findViewById(R.id.value))
                .setText(getOrientationString(mPaperOrientation));

        ((TextView) rootView.findViewById(R.id.scaling).findViewById(R.id.value))
                .setText(getScalingString(mScaling));

        ((TextView) rootView.findViewById(R.id.manual_duplex).findViewById(R.id.value))
                .setText(getManualDuplexString(mManualDuplex));

        // 手动双面不使用双面设置
        if (mManualDuplex == MANUAL_DUPLEX_SETTING_ON) {
            mJob.setPrintConfiguration(Configuration.Duplex, CanonPrintJob.PRINTJOB_INVALID_VALUE);
            updateConfigText(
                    R.id.duplex, R.array.duplexPrint,
                    mJob.getPrintConfiguration(Configuration.Duplex)
            );
        }

        // Refresh
        if (mJob.getPrintConfiguration(Configuration.PaperSize) != CanonPrintParams.PAPERSIZE_USERDEFINITION) {
            rootView.findViewById(R.id.size_width).setVisibility(View.GONE);
            rootView.findViewById(R.id.size_height).setVisibility(View.GONE);
        }
    }

    private void updateConfigText(int layoutId, int stringArrayId, int index) {
        String[] array = getResources().getStringArray(stringArrayId);

        String value;
        if (index < 1 || index > array.length) {
            value = getString(R.string.none);
        } else {
            value = array[index - 1];
        }
        ((TextView) rootView.findViewById(layoutId).findViewById(R.id.value)).setText(value);
    }

    private void openScalingConfigDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle(R.string.select_scaling_setting);

        builder.setItems(R.array.scalingSetting, (dialog, which) -> {
            if (which == 0) {
                mScaling = CanonPrintParams.SCALING_NONE;
            } else if (which == 1) {
                mScaling = CanonPrintParams.SCALING_FIT;
            }
            updateConfigTexts();
        });
        builder.show();
    }

    private String getScalingString(int param) {
        String[] orientationSettings = getResources().getStringArray(R.array.scalingSetting);
        switch (param) {
            case CanonPrintParams.SCALING_NONE:
                return orientationSettings[0]; // Not scaling
            case CanonPrintParams.SCALING_FIT:
                return orientationSettings[1]; // Fit
            default:
                return getString(R.string.none);
        }
    }

    private void openOrientationConfigDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle(R.string.select_orientation_setting);

        builder.setItems(R.array.orientationSetting, (dialog, which) -> {
            if (which == 0) {
                mPaperOrientation = CanonPrintParams.PAPERORIENTATION_AUTOMATIC;
            }
            updateConfigTexts();
        });
        builder.show();
    }

    private String getOrientationString(int param) {
        String[] orientationSettings = getResources().getStringArray(R.array.orientationSetting);
        if (param == CanonPrintParams.PAPERORIENTATION_AUTOMATIC) {
            return orientationSettings[0]; // Automatic
        }
        return getString(R.string.none);
    }

    private void openManualDuplexConfigDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle("选择手动双面设置");

        String[] strings = {
                getManualDuplexString(CanonPrintParams.MANUAL_DUPLEX_SETTING_OFF),
                getManualDuplexString(MANUAL_DUPLEX_SETTING_ON),
        };

        builder.setItems(strings, (dialog, which) -> {
            if (which == MANUAL_DUPLEX_SETTING_OFF) {
                mManualDuplex = CanonPrintParams.MANUAL_DUPLEX_SETTING_OFF;
            } else if (which == MANUAL_DUPLEX_SETTING_ON) {
                mManualDuplex = MANUAL_DUPLEX_SETTING_ON;
            }
            updateConfigTexts();
        });
        builder.show();
    }

    private String getManualDuplexString(int orientation) {
        switch (orientation) {
            case CanonPrintParams.MANUAL_DUPLEX_SETTING_OFF:
                return "不使用";
            case MANUAL_DUPLEX_SETTING_ON:
                return "使用";
            default:
                return "无";
        }
    }

    private void showToast(final String message) {
        getActivity().runOnUiThread(
                () -> Toast.makeText(
                        getContext(),
                        message,
                        Toast.LENGTH_SHORT
                ).show()
        );
    }

    private void print(@NonNull final CanonPrintDevice printer) {
        // Sets a print file
        mJob.setPrintFile(mSelectedFile, mPaperOrientation, mScaling, getContext());

        final Button cancelButton = rootView.findViewById(R.id.cancel_button);
        cancelButton.setOnClickListener(v -> {
            mJob.cancel();
            v.setEnabled(false);
        });
        cancelButton.setEnabled(true);

        // Executes printing
        if (!printer.print(mJob, new CanonPrintCallback() {
            @Override
            public void onChangedJobStatus(final CanonPrintJob job) {
                final CanonPrintJob.PrintJobStatus changedJobStatus = job.getStatus();
                mHandler.post(() -> {
                    String printedCountInfo = getString(R.string.printed_count_info,
                            job.getTotalPrintedCopy(),
                            job.getPrintConfiguration(Configuration.Copies),
                            job.getTotalPrintedPage(),
                            job.getTotalPage()
                    );
                    // Just shows the current status
                    TextView textView = rootView.findViewById(R.id.job_status_text);
                    textView.setText(getString(R.string.job_status,
                            updateJobStatusTexts(changedJobStatus.ordinal()),
                            String.format(Locale.ENGLISH, "%3.2f", (float) (job.getJobProgress() * 100.0f)),
                            printedCountInfo
                    ));

                    // 手动双面打印翻页
                    if (changedJobStatus == CanonPrintJob.PrintJobStatus.WAIT_FLIP_OVER) {
                        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                        builder.setTitle(mSelectedPrinter.toString());
                        //设置对话框内容
                        builder.setMessage("按照指令准备打印另一面。" +
                                "准备好后，单机“开始打印”继续打印。");
                        //"确定"按钮单击事件，退出当前程序
                        builder.setPositiveButton("开始打印", (dialog, which) -> executeManualDuplexPrint());
                        builder.setCancelable(false);
                        builder.show();
                    }

                    // Initializes the text to "Ready" when job has been finished
                    if (job.isFinished()) {
                        rootView.findViewById(R.id.print_button).setEnabled(true);
                        rootView.findViewById(R.id.cancel_button).setEnabled(false);
                        textView.setText(R.string.ready);
                        showToast(getString(R.string.job_result,
                                updateJobStatusTexts(changedJobStatus.ordinal()),
                                printedCountInfo
                        ));
                        textView.setText(R.string.ready);
                        // Re-creates a job
                        mJob = createJob();
                        updateConfigTexts();
                    }
                });
            }

            @Override
            public void onChangedPrinterStatus(CanonPrintJob job, final CanonPrinterStatus status) {
                mHandler.post(() -> {
                    // Just shows the current status
                    String text = updateDeviceStatusTexts(status.getStatus().ordinal());
                    if (!status.getSupportCode().isEmpty()) {
                        text += getString(R.string.support_code, status.getSupportCode());
                    }
                    ((TextView) rootView.findViewById(R.id.printer_status_text)).setText(text);
                });
            }
        })) {
            rootView.findViewById(R.id.print_button).setEnabled(true);
            cancelButton.setEnabled(false);
            mJob = createJob();
            updateConfigTexts();
            showToast("打印失败");
        }
    }

    private void executeManualDuplexPrint() {
        // ManualDuplex setting(Reverse Side)
        mJob.setPrintConfiguration(Configuration.ManualDuplex, CanonPrintParams.MANUAL_DUPLEX_SETTING_REVERSE_SIDE);
        mSelectedPrinter.print(mJob, new CanonPrintCallback() {
            @Override
            public void onChangedJobStatus(final CanonPrintJob job) {
                final CanonPrintJob.PrintJobStatus changedJobStatus = job.getStatus();
                mHandler.post(() -> {
                    String printedCountInfo = "[已打印: "
                            + job.getTotalPrintedCopy() + " / " + job.getPrintConfiguration(Configuration.Copies) + " 份, "
                            + job.getTotalPrintedPage() + " / " + job.getTotalPage() + " 页]";

                    // Just shows the current status
                    TextView textView = (TextView) rootView.findViewById(R.id.job_status_text);
                    String text = updateJobStatusTexts(changedJobStatus.ordinal()) + " "
                            + String.format(Locale.ENGLISH, "%3.2f", (float) (job.getJobProgress() * 100.0f))
                            + "% " + printedCountInfo;
                    textView.setText(text);

                    // Initializes the text to "Ready" when job has been finished
                    if (job.isFinished()) {
                        rootView.findViewById(R.id.print_button).setEnabled(true);
                        textView.setText(R.string.ready);
                        showToast("结果： " + updateJobStatusTexts(changedJobStatus.ordinal()) + " " + printedCountInfo);
                        textView.setText(R.string.ready);
                        // Re-creates a job
                        mJob = createJob();
                        updateConfigTexts();
                    }
                });
            }

            @Override
            public void onChangedPrinterStatus(CanonPrintJob job, final CanonPrinterStatus status) {
                mHandler.post(() -> {
                    // Just shows the current status
                    String text = updateDeviceStatusTexts(status.getStatus().ordinal());
                    if (!status.getSupportCode().isEmpty()) {
                        text += getString(R.string.support_code, status.getSupportCode());
                    }
                    ((TextView) rootView.findViewById(R.id.printer_status_text)).setText(text);
                });
            }
        });
    }

    private void executePrint() {
        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_selected));
            return;
        }

        if (mSelectedFile == null || mSelectedFile == Uri.EMPTY) {
            showToast(getString(R.string.file_is_not_selected));
            return;
        }

        // Check the width and height of custom paper size
        if (mJob.getPrintConfiguration(Configuration.PaperSize) == CanonPrintParams.PAPERSIZE_USERDEFINITION && !checkAndSetCustomPaperSize()) {
            return;
        }

        // ManualDuplex setting
        mJob.setPrintConfiguration(Configuration.ManualDuplex, mManualDuplex);

        // Checks print configuration
        PrintableStatus checkStatus = mSelectedPrinter.isPrintableConfiguration(mJob);
        if (checkStatus != PrintableStatus.OK) {
            String checkMsg;
            switch (checkStatus) {
                case CONFLICT_BORDERLESS:
                    checkMsg = "无边距设置与所设介质和尺寸不匹配";
                    break;
                case CONFLICT_COLOR:
                    checkMsg = "色彩设置与所设介质不匹配";
                    break;
                case CONFLICT_DUPLEX:
                    checkMsg = "双面设置与所设介质和尺寸不匹配";
                    break;
                case CONFLICT_QUALITY:
                    checkMsg = "质量设置与所设介质不匹配";
                    break;
                case INAPPLICABLE_MEDIA:
                    checkMsg = "不适用的介质";
                    break;
                case INAPPLICABLE_SIZE:
                    checkMsg = "不适用的纸张尺寸";
                    break;
                case INAPPLICABLE_COPIES:
                    checkMsg = "不适用的打印份数";
                    break;
                case NULL_CAPABILITY:
                    checkMsg = "打印机能力获取失败";
                    break;
                case ERROR_CONFIGURATION:
                    checkMsg = "打印机配置错误";
                    break;
                case ERROR_USER_DEFINITION:
                    checkMsg = "自定义用纸尺寸设置错误";
                    break;
                default:
                    checkMsg = "其他无效的打印设置";
                    break;
            }
            showToast(checkMsg);
            return;
        }

        // Disables/enables buttons
        rootView.findViewById(R.id.print_button).setEnabled(false);

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            print(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    print(device);
                                } else {
                                    rootView.findViewById(R.id.print_button).setEnabled(true);
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private boolean checkAndSetCustomPaperSize() {
        EditText widthEditText = (EditText) rootView.findViewById(R.id.editText_width);
        int width;
        try {
            width = Integer.parseInt(widthEditText.getText().toString());
        } catch (NumberFormatException e) {
            showToast("请输入正确的宽度。");
            return false;
        }

        int height;
        EditText heightEditText = (EditText) rootView.findViewById(R.id.editText_height);
        try {
            height = Integer.parseInt(heightEditText.getText().toString());
        } catch (NumberFormatException e) {
            showToast("请输入正确的高度。");
            return false;
        }

        int minWidth;
        int maxWidth;
        int minHeight;
        int maxHeight;
        final Hashtable<Configuration, ArrayList<Integer>> params = mSelectedPrinter.getParameterList();
        if (params == null) {
            return false;
        }
        List<Integer> customSize = params.get(Configuration.UserDefinitionSize);
        if (customSize == null || customSize.size() != 4) {
            return false;
        }
        Integer[] sizeArray = customSize.toArray(new Integer[4]);
        minWidth = sizeArray[0] / 100;
        maxWidth = sizeArray[1] / 100;
        minHeight = sizeArray[2] / 100;
        maxHeight = sizeArray[3] / 100;
        if (minWidth > width || width > maxWidth) {
            showToast("请输入正确的宽度。");
            return false;
        }
        if (minHeight > height || height > maxHeight) {
            showToast("请输入正确的高度。");
            return false;
        }
        mJob.setPrintConfiguration(Configuration.CustomWidth, width * 100);
        mJob.setPrintConfiguration(Configuration.CustomHeight, height * 100);
        return true;
    }

    private void prepare(@NonNull final CanonPrintDevice printer) {
        final CountDownLatch latch = new CountDownLatch(1);

        scheduledExecutorService.execute(() -> {
            if (printer.prepare()) {
                showToast(getString(R.string.succeeded_to_prepare));
            } else {
                showToast(getString(R.string.failed_to_prepare));
            }
            latch.countDown();
        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            // Ignore exception
        }

        setSelectedPrinter(printer);
    }

    private void executePrepare() {

        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_granted));
            return;
        }

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            prepare(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    prepare(device);
                                } else {
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private void getStatus(@NonNull final CanonPrintDevice printer) {
        scheduledExecutorService.execute(() -> printer.getStatus(new CanonStatusCallback() {
            @Override
            public void onGotStatus(final CanonPrinterStatus status) {
                openStatusDialog(status);
            }
        }));
    }

    private void executeGetStatus() {

        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_granted));
            return;
        }

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            getStatus(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    getStatus(device);
                                } else {
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private void cleanUp(@NonNull final CanonPrintDevice printer) {
        scheduledExecutorService.execute(() -> {
            if (!printer.cleanUp(mJob, new CanonCleanUpCallback() {
                @Override
                public void onCleanUp(final boolean result) {
                    if (result) {
                        showToast("清理成功。");
                    } else {
                        showToast("清理失败。");
                    }
                }
            })) {
                showToast("无需清理。");
            }
        });
    }

    private void executeCleanUp() {

        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_granted));
            return;
        }

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            cleanUp(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    cleanUp(device);
                                } else {
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private void reset(@NonNull final CanonPrintDevice printer) {
        scheduledExecutorService.execute(() -> {
            printer.reset();
            showToast(getString(R.string.reset_is_executed));
        });
    }

    private void executeReset() {

        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_granted));
            return;
        }

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            reset(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    reset(device);
                                } else {
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private void powerOff(@NonNull final CanonPrintDevice printer) {
        scheduledExecutorService.execute(() -> printer.execControlCommand(ControlCommand.PowerOff, new CanonControlCallback() {
            @Override
            public void onProcessedControlCommand(ControlCommand mode, final ControlResult result) {
                showToast(getString(R.string.power_off_result, updateResultTexts(result.ordinal())));
            }
        }));
    }

    private void executePowerOff() {

        final Context applicationContext = this.getContext();

        if (mSelectedPrinter == null) {
            showToast(getString(R.string.printer_is_not_granted));
            return;
        }

        if (CanonUsbManager.hasPermission(applicationContext, mSelectedPrinter)) {
            powerOff(mSelectedPrinter);
        } else {
            boolean result = CanonUsbManager.requestPermission(
                    applicationContext,
                    mSelectedPrinter,
                    new CanonPermissionRequestCallback() {
                        @Override
                        public void onReceivePermissionGranted(CanonPrintDevice device, boolean isGranted) {
                            if (mSelectedPrinter.equals(device)) {
                                if (isGranted) {
                                    powerOff(device);
                                } else {
                                    showToast(getString(R.string.printer_is_not_granted));
                                }
                            }
                        }
                    }
            );
            if (!result) {
                showToast(getString(R.string.failed_to_request_permission));
            }
        }
    }

    private void openStatusDialog(final CanonPrinterStatus status) {
        getActivity().runOnUiThread(
                () -> {

                    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                    // set printer status in title
                    String text = getString(R.string.printer_status, updateDeviceStatusTexts(status.getStatus().ordinal()));
                    if (!status.getSupportCode().isEmpty()) {
                        text += getString(R.string.support_code, status.getSupportCode());
                    }
                    builder.setTitle(text);

                    List<String> statusText = new ArrayList<>();
                    // set inkInfos
                    CanonPrinterInkInfo[] inkInfos = status.getInkInfos();
                    if (inkInfos != null) {
                        statusText.add(getString(R.string.ink_info_title));
                        for (CanonPrinterInkInfo inkInfo : status.getInkInfos()) {
                            statusText.add(getString(R.string.ink_info,
                                    inkInfo.getOrder(),
                                    inkInfo.getModel(),
                                    inkInfo.getColor(),
                                    inkInfo.getLevel(),
                                    inkInfo.getInkstatus()
                            ));
                        }
                    } else {
                        // if cannot get ink status
                        statusText.add(getString(R.string.no_ink_info));
                    }

                    CanonPrinterBatteryInfo batteryInfo = status.getBatteryInfo();
                    StringBuilder buf = new StringBuilder();
                    // set batteryInfo
                    if (batteryInfo != null) {
                        statusText.add("电池信息");
                        buf.append("剩余电量: ").append(batteryInfo.getLevel());
                        buf.append("\n电池状态: ");
                        for (int i : batteryInfo.getBatterystatus()) {
                            buf.append(i).append(", ");
                        }
                        statusText.add(buf.toString());
                        buf.setLength(0);
                    } else {
                        // if cannot get battery status
                        statusText.add("没有电池信息");
                    }

                    String[] statusArray = statusText.toArray(new String[0]);

                    builder.setItems(statusArray, (dialog, which) -> {// nop
                    });
                    builder.show();
                }
        );
    }

    private String updateConfigurationTexts(int index) {
        String[] configurationDisplayNames = this.getContext().getResources().getStringArray(R.array.configuration);
        if (index > configurationDisplayNames.length) {
            return "";
        }
        return configurationDisplayNames[index];
    }

    private String updateResultTexts(int index) {
        String[] resultNames = this.getContext().getResources().getStringArray(R.array.control_result);
        if (index > resultNames.length) {
            return "";
        }
        return resultNames[index];
    }

    private String updateDeviceStatusTexts(int index) {
        String[] deviceStatusNames = this.getContext().getResources().getStringArray(R.array.device_status);
        if (index > deviceStatusNames.length) {
            return "";
        }
        return deviceStatusNames[index];
    }

    private String updateJobStatusTexts(int index) {
        String[] jobStatusNames = this.getContext().getResources().getStringArray(R.array.job_status);
        if (index > jobStatusNames.length) {
            return "";
        }
        return jobStatusNames[index];
    }
}

